{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "16f80216-52cd-4d63-9c9b-542bb79b5999",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "这个用户可能偏好喜剧片\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from sklearn.neighbors import KNeighborsClassifier\n",
    "\n",
    "# 1. 构造数据\n",
    "X = np.array([[5, 1], [4, 2], [2, 5], [1, 4], [3, 2], [2, 5]])\n",
    "y = np.array([0, 0, 1, 1, 0, 1])  # 0 表示动作片，1 表示喜剧片\n",
    "\n",
    "# 2. 创建 KNN 模型\n",
    "k = 1\n",
    "knn = KNeighborsClassifier(n_neighbors=k)\n",
    "\n",
    "# 3. 训练模型\n",
    "knn.fit(X, y)\n",
    "\n",
    "# 4. 用模型推理（预测）用户的喜好\n",
    "new_user = np.array([[3, 4]])\n",
    "prediction = knn.predict(new_user)\n",
    "\n",
    "# 输出预测结果\n",
    "if prediction[0] == 0:\n",
    "    print(\"这个用户可能偏好动作片\")\n",
    "else:\n",
    "    print(\"这个用户可能偏好喜剧片\")\n",
    "\n",
    "# 5. 数据可视化\n",
    "plt.title(\"User Movie Preference\", size=15)\n",
    "plt.xlabel(\"Movie A rating\")\n",
    "plt.ylabel(\"Movie B rating\")\n",
    "plt.grid()\n",
    "\n",
    "# 绘制原始样本点，不同类别用不同颜色和标记\n",
    "action_movies = X[y == 0]\n",
    "comedy_movies = X[y == 1]\n",
    "plt.scatter(action_movies[:, 0], action_movies[:, 1], color='blue', marker='o', label='Action')\n",
    "plt.scatter(comedy_movies[:, 0], comedy_movies[:, 1], color='red', marker='x', label='Comedy')\n",
    "\n",
    "# 绘制新用户的点\n",
    "plt.scatter(new_user[0, 0], new_user[0, 1], color='green', marker='*', s=100, label='New User')\n",
    "\n",
    "# 获取最近邻的坐标并绘制连线\n",
    "distances, indices = knn.kneighbors(new_user)\n",
    "nearest_neighbor = X[indices[0]]\n",
    "plt.plot([new_user[0, 0], nearest_neighbor[0, 0]], [new_user[0, 1], nearest_neighbor[0, 1]], 'r--')\n",
    "\n",
    "# 为每个点添加坐标文本\n",
    "for x, y, label in zip(X[:, 0], X[:, 1], y):\n",
    "    plt.text(x, y, f'({x}, {y})', fontsize=9)\n",
    "plt.text(new_user[0, 0], new_user[0, 1], f'({new_user[0, 0]}, {new_user[0, 1]})', fontsize=9)\n",
    "\n",
    "plt.legend()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "bc58aba3-5d0a-45ef-bdb4-61851dcea0fd",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from sklearn.neighbors import KNeighborsClassifier\n",
    "\n",
    "# 构造数据（假设的电影评分数据，特征为电影A和电影B的评分，标签可根据实际场景定义，这里主要演示KNN找最近邻绘图）\n",
    "X = np.array([[1, 4], [2, 5], [3, 2], [4, 2], [2, 3], [3, 4]])\n",
    "# 新的待测点\n",
    "new_point = np.array([[3, 4]])\n",
    "\n",
    "# 创建KNN模型，n_neighbors=1表示找最近的1个邻居\n",
    "knn = KNeighborsClassifier(n_neighbors=1)\n",
    "# 训练模型（这里为了演示，用X自身训练，实际场景应有对应的标签数据等，此处重点在找最近邻绘图）\n",
    "knn.fit(X, np.zeros(X.shape[0]))\n",
    "\n",
    "# 获取最近邻的索引\n",
    "distances, indices = knn.kneighbors(new_point)\n",
    "nearest_neighbor = X[indices[0][0]]\n",
    "\n",
    "# 绘制所有的数据点\n",
    "plt.scatter(X[:, 0], X[:, 1], color='blue', label='Data Points')\n",
    "# 绘制新的待测点\n",
    "plt.scatter(new_point[0, 0], new_point[0, 1], color='red', marker='*', s=100, label='New Point')\n",
    "# 绘制最近邻点\n",
    "plt.scatter(nearest_neighbor[0], nearest_neighbor[1], color='green', marker='x', s=100, label='Nearest Neighbor')\n",
    "# 绘制新待测点与最近邻的连线\n",
    "plt.plot([new_point[0, 0], nearest_neighbor[0]], [new_point[0, 1], nearest_neighbor[1]], 'r--')\n",
    "\n",
    "plt.title('User Movie Preference')\n",
    "plt.xlabel('Movie A Rating')\n",
    "plt.ylabel('Movie B Rating')\n",
    "plt.legend()\n",
    "plt.grid(True)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "f8b27424-8929-4855-b38a-c20a13f0f70d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "用户可能偏好：喜剧片\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from sklearn.neighbors import KNeighborsClassifier\n",
    "\n",
    "# 1. 构造数据（电影A评分、电影B评分）\n",
    "X = np.array([[5, 1], [4, 2], [2, 5], [1, 4], [3, 2], [2, 5]])\n",
    "# 电影类型：0 动作片，1 喜剧片\n",
    "y = np.array([0, 0, 1, 1, 0, 1])  \n",
    "\n",
    "# 2. 创建 KNN 模型\n",
    "k = 1\n",
    "knn = KNeighborsClassifier(n_neighbors=k)\n",
    "\n",
    "# 3. 训练模型\n",
    "knn.fit(X, y)\n",
    "\n",
    "# 4. 新用户数据（电影A评3分，电影B评4分）\n",
    "new_user = np.array([[3, 4]])\n",
    "# 预测喜好类型\n",
    "prediction = knn.predict(new_user)\n",
    "\n",
    "# 输出预测结果\n",
    "print(\"用户可能偏好：\" + (\"喜剧片\" if prediction[0] == 1 else \"动作片\"))\n",
    "\n",
    "# 5. 数据可视化\n",
    "plt.title(\"User Movie Preference\", size=15)\n",
    "plt.xlabel(\"Movie A Rating\")\n",
    "plt.ylabel(\"Movie B Rating\")\n",
    "plt.grid()\n",
    "\n",
    "# 绘制原始数据点（动作片蓝色圈，喜剧片红色叉）\n",
    "action_points = X[y == 0]\n",
    "comedy_points = X[y == 1]\n",
    "plt.scatter(action_points[:, 0], action_points[:, 1], color='blue', marker='o', label='Action')\n",
    "plt.scatter(comedy_points[:, 0], comedy_points[:, 1], color='red', marker='x', label='Comedy')\n",
    "\n",
    "# 绘制新用户点（绿色星标）\n",
    "plt.scatter(new_user[0, 0], new_user[0, 1], color='green', marker='*', s=100, label='New User')\n",
    "\n",
    "# 动态获取最近邻并绘制连线\n",
    "distances, indices = knn.kneighbors(new_user)\n",
    "nearest_point = X[indices[0][0]]\n",
    "plt.plot([new_user[0, 0], nearest_point[0]], [new_user[0, 1], nearest_point[1]], 'r--')\n",
    "\n",
    "# 标注所有点的坐标\n",
    "for x, y in X:\n",
    "    plt.text(x, y, f'({x},{y})', fontsize=9)\n",
    "plt.text(new_user[0, 0], new_user[0, 1], f'({new_user[0, 0]},{new_user[0, 1]})', fontsize=9)\n",
    "\n",
    "plt.legend()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "7862d42c-74f7-4de8-86bd-47f950f0fa40",
   "metadata": {},
   "outputs": [
    {
     "ename": "TclError",
     "evalue": "no display name and no $DISPLAY environment variable",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mTclError\u001b[0m                                  Traceback (most recent call last)",
      "\u001b[0;32m/tmp/ipykernel_139/2969732165.py\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m    158\u001b[0m         \u001b[0mcanvas\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_tk_widget\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpack\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    159\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 160\u001b[0;31m \u001b[0mroot\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtk\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mTk\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    161\u001b[0m \u001b[0mapp\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mMovieRatingApp\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mroot\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    162\u001b[0m \u001b[0mroot\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmainloop\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;32m/opt/conda/lib/python3.9/tkinter/__init__.py\u001b[0m in \u001b[0;36m__init__\u001b[0;34m(self, screenName, baseName, className, useTk, sync, use)\u001b[0m\n\u001b[1;32m   2268\u001b[0m                 \u001b[0mbaseName\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mbaseName\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mext\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   2269\u001b[0m         \u001b[0minteractive\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 2270\u001b[0;31m         \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtk\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0m_tkinter\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcreate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mscreenName\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mbaseName\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mclassName\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minteractive\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwantobjects\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0museTk\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msync\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0muse\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m   2271\u001b[0m         \u001b[0;32mif\u001b[0m \u001b[0museTk\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m   2272\u001b[0m             \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_loadtk\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;31mTclError\u001b[0m: no display name and no $DISPLAY environment variable"
     ]
    }
   ],
   "source": [
    "import tkinter as tk\n",
    "import pandas as pd\n",
    "from sklearn.neighbors import KNeighborsClassifier\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from PIL import Image, ImageTk\n",
    "import os\n",
    "from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg\n",
    "import logging\n",
    "\n",
    "logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')\n",
    "\n",
    "class MovieRatingApp:\n",
    "    def __init__(self, root):\n",
    "        self.root = root\n",
    "        self.root.title(\"Movie Rating App\")\n",
    "        self.root.geometry(\"900x600\")\n",
    "\n",
    "        # 加载电影1图片\n",
    "        self.image1 = Image.open(os.path.join(os.path.dirname(__file__), 'post1_pic.jpg'))\n",
    "        self.image1 = self.image1.resize((100, 150))\n",
    "        self.movie1_image = ImageTk.PhotoImage(self.image1)\n",
    "        self.movie1_label = tk.Label(root, image=self.movie1_image)\n",
    "        self.movie1_label.grid(row=0, column=0, padx=10, pady=10)\n",
    "\n",
    "        # 加载电影2图片\n",
    "        self.image2 = Image.open(os.path.join(os.path.dirname(__file__), 'post2_pic.jpg'))\n",
    "        self.image2 = self.image2.resize((100, 150))\n",
    "        self.movie2_image = ImageTk.PhotoImage(self.image2)\n",
    "        self.movie2_label = tk.Label(root, image=self.movie2_image)\n",
    "        self.movie2_label.grid(row=0, column=1, padx=10, pady=10)\n",
    "\n",
    "        # 电影1评分下拉框\n",
    "        self.movie1_rating = tk.StringVar(root, value=\"1\")\n",
    "        tk.OptionMenu(root, self.movie1_rating, \"1\", \"2\", \"3\", \"4\", \"5\").grid(row=1, column=0, padx=10, pady=10)\n",
    "\n",
    "        # 电影2评分下拉框\n",
    "        self.movie2_rating = tk.StringVar(root, value=\"1\")\n",
    "        tk.OptionMenu(root, self.movie2_rating, \"1\", \"2\", \"3\", \"4\", \"5\").grid(row=1, column=1, padx=10, pady=10)\n",
    "\n",
    "        # 电影类型单选按钮\n",
    "        self.preference = tk.IntVar()\n",
    "        tk.Radiobutton(root, text=\"动作片\", variable=self.preference, value=0).grid(row=2, column=0, padx=10, pady=10)\n",
    "        tk.Radiobutton(root, text=\"喜剧片\", variable=self.preference, value=1).grid(row=2, column=1, padx=10, pady=10)\n",
    "\n",
    "        # 确认按钮\n",
    "        self.confirm_btn = tk.Button(root, text=\"Confirm\", command=self.confirm)\n",
    "        self.confirm_btn.grid(row=3, column=0, padx=10, pady=10)\n",
    "\n",
    "        # 预测按钮\n",
    "        self.predict_btn = tk.Button(root, text=\"Predict\", command=self.predict_preference)\n",
    "        self.predict_btn.grid(row=3, column=1, padx=10, pady=10)\n",
    "\n",
    "        # 预测结果标签\n",
    "        self.predict_label = tk.Label(root, text=\"\")\n",
    "        self.predict_label.grid(row=4, column=0, columnspan=2, padx=10, pady=10)\n",
    "\n",
    "        # 评分数据文件\n",
    "        self.filename = os.path.join(os.path.dirname(__file__), 'ratings.csv')\n",
    "        if not os.path.isfile(self.filename):\n",
    "            with open(self.filename, 'w', newline='') as file:\n",
    "                writer = pd.DataFrame(columns=[\"Movie1\", \"Movie2\", \"Preference\"]).to_csv(file, index=False)\n",
    "\n",
    "        # 绘图框架\n",
    "        self.plot_frame = tk.Frame(root)\n",
    "        self.plot_frame.grid(row=0, column=2, rowspan=5, padx=10, pady=10)\n",
    "\n",
    "    def confirm(self):\n",
    "        m1_rating = self.movie1_rating.get()\n",
    "        m2_rating = self.movie2_rating.get()\n",
    "        preference = self.preference.get()\n",
    "\n",
    "        df = pd.DataFrame([[m1_rating, m2_rating, preference]], columns=[\"Movie1\", \"Movie2\", \"Preference\"])\n",
    "        df.to_csv(self.filename, mode='a', header=False, index=False)\n",
    "\n",
    "        self.plot_ratings()\n",
    "\n",
    "    def clear_plot(self):\n",
    "        for widget in self.plot_frame.winfo_children():\n",
    "            widget.destroy()\n",
    "        self.plot_frame = tk.Frame(root)\n",
    "        self.plot_frame.grid(row=0, column=2, rowspan=5, padx=10, pady=10)\n",
    "\n",
    "    def plot_ratings(self):\n",
    "        self.clear_plot()\n",
    "\n",
    "        df = pd.read_csv(self.filename)\n",
    "        action_movies = df[df[\"Preference\"] == 0]\n",
    "        comedy_movies = df[df[\"Preference\"] == 1]\n",
    "\n",
    "        fig, ax = plt.subplots(figsize=(6, 6))\n",
    "        ax.grid(True)\n",
    "        ax.scatter(action_movies[\"Movie1\"], action_movies[\"Movie2\"], color='orange', label='Action')\n",
    "        ax.scatter(comedy_movies[\"Movie1\"], comedy_movies[\"Movie2\"], color='blue', label='Comedy')\n",
    "\n",
    "        for i in range(len(action_movies)):\n",
    "            ax.text(action_movies[\"Movie1\"].iloc[i], action_movies[\"Movie2\"].iloc[i], \n",
    "                    f'({action_movies[\"Movie1\"].iloc[i]}, {action_movies[\"Movie2\"].iloc[i]})', fontsize=8)\n",
    "        for i in range(len(comedy_movies)):\n",
    "            ax.text(comedy_movies[\"Movie1\"].iloc[i], comedy_movies[\"Movie2\"].iloc[i], \n",
    "                    f'({comedy_movies[\"Movie1\"].iloc[i]}, {comedy_movies[\"Movie2\"].iloc[i]})', fontsize=8)\n",
    "\n",
    "        ax.set_xlabel(\"Movie1 Rating\")\n",
    "        ax.set_ylabel(\"Movie2 Rating\")\n",
    "        ax.set_xlim(0, 6)\n",
    "        ax.set_ylim(0, 6)\n",
    "        ax.legend()\n",
    "\n",
    "        canvas = FigureCanvasTkAgg(fig, master=self.plot_frame)\n",
    "        canvas.draw()\n",
    "        canvas.get_tk_widget().pack()\n",
    "\n",
    "    def predict_preference(self):\n",
    "        df = pd.read_csv(self.filename)\n",
    "        X = df[[\"Movie1\", \"Movie2\"]].astype(int).values\n",
    "        y = df[\"Preference\"].values\n",
    "\n",
    "        knn = KNeighborsClassifier(n_neighbors=1)\n",
    "        knn.fit(X, y)\n",
    "\n",
    "        m1_rating = int(self.movie1_rating.get())\n",
    "        m2_rating = int(self.movie2_rating.get())\n",
    "        new_point = np.array([[m1_rating, m2_rating]])\n",
    "\n",
    "        prediction = knn.predict(new_point)\n",
    "        self.predict_label.config(text=f\"给用户推荐的电影类型：{'动作片' if prediction[0] == 0 else '喜剧片'}\")\n",
    "\n",
    "        self.plot_ratings()\n",
    "\n",
    "        fig, ax = plt.subplots(figsize=(6, 6))\n",
    "        ax.grid(True)\n",
    "        action_movies = df[df[\"Preference\"] == 0]\n",
    "        comedy_movies = df[df[\"Preference\"] == 1]\n",
    "        ax.scatter(action_movies[\"Movie1\"], action_movies[\"Movie2\"], color='orange', label='Action')\n",
    "        ax.scatter(comedy_movies[\"Movie1\"], comedy_movies[\"Movie2\"], color='blue', label='Comedy')\n",
    "        ax.scatter(new_point[:, 0], new_point[:, 1], color='red', marker='x')\n",
    "\n",
    "        distances, indices = knn.kneighbors(new_point)\n",
    "        nearest_neighbor = X[indices[0]]\n",
    "        ax.plot([new_point[0, 0], nearest_neighbor[0, 0]], [new_point[0, 1], nearest_neighbor[1]], 'r--')\n",
    "\n",
    "        for i in range(len(action_movies)):\n",
    "            ax.text(action_movies[\"Movie1\"].iloc[i], action_movies[\"Movie2\"].iloc[i], \n",
    "                    f'({action_movies[\"Movie1\"].iloc[i]}, {action_movies[\"Movie2\"].iloc[i]})', fontsize=8)\n",
    "        for i in range(len(comedy_movies)):\n",
    "            ax.text(comedy_movies[\"Movie1\"].iloc[i], comedy_movies[\"Movie2\"].iloc[i], \n",
    "                    f'({comedy_movies[\"Movie1\"].iloc[i]}, {comedy_movies[\"Movie2\"].iloc[i]})', fontsize=8)\n",
    "        ax.text(new_point[0, 0], new_point[0, 1], f'({new_point[0, 0]}, {new_point[0, 1]})', fontsize=8)\n",
    "\n",
    "        ax.set_xlabel(\"Movie1 Rating\")\n",
    "        ax.set_ylabel(\"Movie2 Rating\")\n",
    "        ax.set_xlim(0, 6)\n",
    "        ax.set_ylim(0, 6)\n",
    "        ax.legend()\n",
    "\n",
    "        canvas = FigureCanvasTkAgg(fig, master=self.plot_frame)\n",
    "        canvas.draw()\n",
    "        canvas.get_tk_widget().pack()\n",
    "\n",
    "root = tk.Tk()\n",
    "app = MovieRatingApp(root)\n",
    "root.mainloop()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "6ede3885-b4fe-4f5a-b4f0-fceb3d1de352",
   "metadata": {},
   "outputs": [
    {
     "ename": "SyntaxError",
     "evalue": "invalid syntax (1259942481.py, line 1)",
     "output_type": "error",
     "traceback": [
      "\u001b[0;36m  File \u001b[0;32m\"/tmp/ipykernel_139/1259942481.py\"\u001b[0;36m, line \u001b[0;32m1\u001b[0m\n\u001b[0;31m    xvfb-run -a python 2_movie_rating_APP.py\u001b[0m\n\u001b[0m                ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m invalid syntax\n"
     ]
    }
   ],
   "source": [
    "xvfb-run -a python 2_movie_rating_APP.py\n",
    "import tkinter as tk\n",
    "import pandas as pd\n",
    "from sklearn.neighbors import KNeighborsClassifier\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from PIL import Image, ImageTk\n",
    "import os\n",
    "from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg\n",
    "import logging\n",
    "\n",
    "logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')\n",
    "\n",
    "class MovieRatingApp:\n",
    "    def __init__(self, root):\n",
    "        self.root = root\n",
    "        self.root.title(\"Movie Rating App\")\n",
    "        self.root.geometry(\"900x600\")\n",
    "\n",
    "        # 加载电影1图片\n",
    "        self.image1 = Image.open(os.path.join(os.path.dirname(__file__), 'post1_pic.jpg'))\n",
    "        self.image1 = self.image1.resize((100, 150))\n",
    "        self.movie1_image = ImageTk.PhotoImage(self.image1)\n",
    "        self.movie1_label = tk.Label(root, image=self.movie1_image)\n",
    "        self.movie1_label.grid(row=0, column=0, padx=10, pady=10)\n",
    "\n",
    "        # 加载电影2图片\n",
    "        self.image2 = Image.open(os.path.join(os.path.dirname(__file__), 'post2_pic.jpg'))\n",
    "        self.image2 = self.image2.resize((100, 150))\n",
    "        self.movie2_image = ImageTk.PhotoImage(self.image2)\n",
    "        self.movie2_label = tk.Label(root, image=self.movie2_image)\n",
    "        self.movie2_label.grid(row=0, column=1, padx=10, pady=10)\n",
    "\n",
    "        # 电影1评分下拉框\n",
    "        self.movie1_rating = tk.StringVar(root, value=\"1\")\n",
    "        tk.OptionMenu(root, self.movie1_rating, \"1\", \"2\", \"3\", \"4\", \"5\").grid(row=1, column=0, padx=10, pady=10)\n",
    "\n",
    "        # 电影2评分下拉框\n",
    "        self.movie2_rating = tk.StringVar(root, value=\"1\")\n",
    "        tk.OptionMenu(root, self.movie2_rating, \"1\", \"2\", \"3\", \"4\", \"5\").grid(row=1, column=1, padx=10, pady=10)\n",
    "\n",
    "        # 电影类型单选按钮\n",
    "        self.preference = tk.IntVar()\n",
    "        tk.Radiobutton(root, text=\"动作片\", variable=self.preference, value=0).grid(row=2, column=0, padx=10, pady=10)\n",
    "        tk.Radiobutton(root, text=\"喜剧片\", variable=self.preference, value=1).grid(row=2, column=1, padx=10, pady=10)\n",
    "\n",
    "        # 确认按钮\n",
    "        self.confirm_btn = tk.Button(root, text=\"Confirm\", command=self.confirm)\n",
    "        self.confirm_btn.grid(row=3, column=0, padx=10, pady=10)\n",
    "\n",
    "        # 预测按钮\n",
    "        self.predict_btn = tk.Button(root, text=\"Predict\", command=self.predict_preference)\n",
    "        self.predict_btn.grid(row=3, column=1, padx=10, pady=10)\n",
    "\n",
    "        # 预测结果标签\n",
    "        self.predict_label = tk.Label(root, text=\"\")\n",
    "        self.predict_label.grid(row=4, column=0, columnspan=2, padx=10, pady=10)\n",
    "\n",
    "        # 评分数据文件\n",
    "        self.filename = os.path.join(os.path.dirname(__file__), 'ratings.csv')\n",
    "        if not os.path.isfile(self.filename):\n",
    "            with open(self.filename, 'w', newline='') as file:\n",
    "                writer = pd.DataFrame(columns=[\"Movie1\", \"Movie2\", \"Preference\"]).to_csv(file, index=False)\n",
    "\n",
    "        # 绘图框架\n",
    "        self.plot_frame = tk.Frame(root)\n",
    "        self.plot_frame.grid(row=0, column=2, rowspan=5, padx=10, pady=10)\n",
    "\n",
    "    def confirm(self):\n",
    "        m1_rating = self.movie1_rating.get()\n",
    "        m2_rating = self.movie2_rating.get()\n",
    "        preference = self.preference.get()\n",
    "\n",
    "        df = pd.DataFrame([[m1_rating, m2_rating, preference]], columns=[\"Movie1\", \"Movie2\", \"Preference\"])\n",
    "        df.to_csv(self.filename, mode='a', header=False, index=False)\n",
    "\n",
    "        self.plot_ratings()\n",
    "\n",
    "    def clear_plot(self):\n",
    "        for widget in self.plot_frame.winfo_children():\n",
    "            widget.destroy()\n",
    "        self.plot_frame = tk.Frame(root)\n",
    "        self.plot_frame.grid(row=0, column=2, rowspan=5, padx=10, pady=10)\n",
    "\n",
    "    def plot_ratings(self):\n",
    "        self.clear_plot()\n",
    "\n",
    "        df = pd.read_csv(self.filename)\n",
    "        action_movies = df[df[\"Preference\"] == 0]\n",
    "        comedy_movies = df[df[\"Preference\"] == 1]\n",
    "\n",
    "        fig, ax = plt.subplots(figsize=(6, 6))\n",
    "        ax.grid(True)\n",
    "        ax.scatter(action_movies[\"Movie1\"], action_movies[\"Movie2\"], color='orange', label='Action')\n",
    "        ax.scatter(comedy_movies[\"Movie1\"], comedy_movies[\"Movie2\"], color='blue', label='Comedy')\n",
    "\n",
    "        for i in range(len(action_movies)):\n",
    "            ax.text(action_movies[\"Movie1\"].iloc[i], action_movies[\"Movie2\"].iloc[i], \n",
    "                    f'({action_movies[\"Movie1\"].iloc[i]}, {action_movies[\"Movie2\"].iloc[i]})', fontsize=8)\n",
    "        for i in range(len(comedy_movies)):\n",
    "            ax.text(comedy_movies[\"Movie1\"].iloc[i], comedy_movies[\"Movie2\"].iloc[i], \n",
    "                    f'({comedy_movies[\"Movie1\"].iloc[i]}, {comedy_movies[\"Movie2\"].iloc[i]})', fontsize=8)\n",
    "\n",
    "        ax.set_xlabel(\"Movie1 Rating\")\n",
    "        ax.set_ylabel(\"Movie2 Rating\")\n",
    "        ax.set_xlim(0, 6)\n",
    "        ax.set_ylim(0, 6)\n",
    "        ax.legend()\n",
    "\n",
    "        canvas = FigureCanvasTkAgg(fig, master=self.plot_frame)\n",
    "        canvas.draw()\n",
    "        canvas.get_tk_widget().pack()\n",
    "\n",
    "    def predict_preference(self):\n",
    "        df = pd.read_csv(self.filename)\n",
    "        X = df[[\"Movie1\", \"Movie2\"]].astype(int).values\n",
    "        y = df[\"Preference\"].values\n",
    "\n",
    "        knn = KNeighborsClassifier(n_neighbors=1)\n",
    "        knn.fit(X, y)\n",
    "\n",
    "        m1_rating = int(self.movie1_rating.get())\n",
    "        m2_rating = int(self.movie2_rating.get())\n",
    "        new_point = np.array([[m1_rating, m2_rating]])\n",
    "\n",
    "        prediction = knn.predict(new_point)\n",
    "        self.predict_label.config(text=f\"给用户推荐的电影类型：{'动作片' if prediction[0] == 0 else '喜剧片'}\")\n",
    "\n",
    "        self.plot_ratings()\n",
    "\n",
    "        fig, ax = plt.subplots(figsize=(6, 6))\n",
    "        ax.grid(True)\n",
    "        action_movies = df[df[\"Preference\"] == 0]\n",
    "        comedy_movies = df[df[\"Preference\"] == 1]\n",
    "        ax.scatter(action_movies[\"Movie1\"], action_movies[\"Movie2\"], color='orange', label='Action')\n",
    "        ax.scatter(comedy_movies[\"Movie1\"], comedy_movies[\"Movie2\"], color='blue', label='Comedy')\n",
    "        ax.scatter(new_point[:, 0], new_point[:, 1], color='red', marker='x')\n",
    "\n",
    "        distances, indices = knn.kneighbors(new_point)\n",
    "        nearest_neighbor = X[indices[0]]\n",
    "        ax.plot([new_point[0, 0], nearest_neighbor[0, 0]], [new_point[0, 1], nearest_neighbor[1]], 'r--')\n",
    "\n",
    "        for i in range(len(action_movies)):\n",
    "            ax.text(action_movies[\"Movie1\"].iloc[i], action_movies[\"Movie2\"].iloc[i], \n",
    "                    f'({action_movies[\"Movie1\"].iloc[i]}, {action_movies[\"Movie2\"].iloc[i]})', fontsize=8)\n",
    "        for i in range(len(comedy_movies)):\n",
    "            ax.text(comedy_movies[\"Movie1\"].iloc[i], comedy_movies[\"Movie2\"].iloc[i], \n",
    "                    f'({comedy_movies[\"Movie1\"].iloc[i]}, {comedy_movies[\"Movie2\"].iloc[i]})', fontsize=8)\n",
    "        ax.text(new_point[0, 0], new_point[0, 1], f'({new_point[0, 0]}, {new_point[0, 1]})', fontsize=8)\n",
    "\n",
    "        ax.set_xlabel(\"Movie1 Rating\")\n",
    "        ax.set_ylabel(\"Movie2 Rating\")\n",
    "        ax.set_xlim(0, 6)\n",
    "        ax.set_ylim(0, 6)\n",
    "        ax.legend()\n",
    "\n",
    "        canvas = FigureCanvasTkAgg(fig, master=self.plot_frame)\n",
    "        canvas.draw()\n",
    "        canvas.get_tk_widget().pack()\n",
    "\n",
    "root = tk.Tk()\n",
    "app = MovieRatingApp(root)\n",
    "root.mainloop()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "298396b0-dfe7-43c6-8fbb-7e3d9fd50df1",
   "metadata": {},
   "outputs": [
    {
     "ename": "SyntaxError",
     "evalue": "invalid syntax (4173140973.py, line 1)",
     "output_type": "error",
     "traceback": [
      "\u001b[0;36m  File \u001b[0;32m\"/tmp/ipykernel_139/4173140973.py\"\u001b[0;36m, line \u001b[0;32m1\u001b[0m\n\u001b[0;31m    xvfb-run -a python 2_movie_rating_APP.py\u001b[0m\n\u001b[0m                ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m invalid syntax\n"
     ]
    }
   ],
   "source": [
    "xvfb-run -a python 2_movie_rating_APP.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cbc3ee31-dccd-4522-b826-201800f0e637",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
